using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Synapse.Framework.AoP;
using Synapse.Framework.Notifier;
using Synapse.Framework.IoC;

namespace Synapse.Framework.Impulse
{
    public class Synapsy : ISynapsy
    {
        private readonly IResolver resolver;
        private readonly IExceptionNotifier exceptionNotifier;
        private readonly List<Type> allTypes;
        private readonly List<ClientTypes> allClientTypes;
        
        public Synapsy(IResolver resolver)
            : this(resolver, new TraceNotifier())
        {
        }

        public Synapsy(IResolver resolver, IExceptionNotifier exceptionNotifier)
        {
            this.resolver = resolver;
            this.exceptionNotifier = exceptionNotifier;
            allTypes = new List<Type>();
            allClientTypes = new List<ClientTypes>();
            foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                try
                {
                    allTypes.AddRange(assembly.GetTypes().Where(t => t.IsClass));
                }
                catch (Exception ex)
                {
                    exceptionNotifier.Notify(ex);
                }
            }
        }

        public ISynapsy Connect<T>(Expression<Action<T>> expression)
        {
            foreach (var client in GetClientTypes<T>())
                Invoke(expression, client);
            return this;
        }

        private IEnumerable<Type> GetClientTypes<T>()
        {
            var clientTypes = allClientTypes.SingleOrDefault(t => t is T);
            if (clientTypes == null)
            {
                clientTypes = new ClientTypes(typeof(T));
                clientTypes.Clients.AddRange(allTypes.Where(t => t.GetInterfaces().Any(i => i == clientTypes.Type)));
            }
            return clientTypes.Clients;
        }

        private void Invoke<T>(Expression<Action<T>> expression, Type client)
        {
                var instance = resolver.Resolve(client);
                var body = ((MethodCallExpression)expression.Body);
                var executeBehaviour = new ExecuteBehaviour(instance);
                var executeCheck = new ExecuteCheck(instance);
                var parameters = GetParameterValues(expression, body);
                var methodInfo = instance.GetType().GetMethods().Single(m => m.ToString() == body.Method.ToString());

                executeBehaviour.BeforeCallMethod(methodInfo, parameters);
                executeCheck.ForParameters(methodInfo, parameters);
                var returnValue = body.Method.Invoke(instance, parameters);
                executeCheck.ForReturnValue(methodInfo, returnValue);
                executeBehaviour.AfterCallMethod(methodInfo, parameters, returnValue);
        }

        private object[] GetParameterValues<T>(Expression<Action<T>> expression, MethodCallExpression body)
        {
            return (from argument in body.Arguments
                    select Expression.Lambda(argument, expression.Parameters)
                    into lambda
                    select lambda.Compile()
                    into d
                    select d.DynamicInvoke(new object[1])).ToArray();
        }
    }
}